package com.piece.core.framework.util.net;

import com.alibaba.fastjson.JSONPath;
import com.piece.core.framework.enums.ContentType;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.framework.util.time.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.safety.Whitelist;
import org.jsoup.select.Elements;
import org.seimicrawler.xpath.JXDocument;
import org.seimicrawler.xpath.JXNode;
import us.codecraft.xsoup.Xsoup;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Html工具类
 */
@Slf4j
public class HtmlUtil {

    private static Map<String, Pattern> patterns = new MaxSizeHashMap<>(100);

    private static class MaxSizeHashMap<K, V> extends LinkedHashMap<K, V> {
        private final int maxSize;

        public MaxSizeHashMap(int maxSize) {
            this.maxSize = maxSize;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > maxSize;
        }
    }

    private static Pattern compile(String regEx) {
        Pattern pattern = patterns.get(regEx);
        if (null == pattern) {
            pattern = Pattern.compile(regEx, Pattern.DOTALL);
            patterns.put(regEx, pattern);
        }
        return pattern;
    }

    /**
     * 获取所有匹配项
     *
     * @param content 文本
     * @param regEx   正则表达式
     * @param isGroup 是否只匹配第一组
     * @return
     */
    public static List<String> getMatchers(String content, String regEx, boolean isGroup) {
        return getMatchers(content, regEx, isGroup ? 1 : 0);
    }

    /**
     * 获取所有匹配项
     *
     * @param content    文本
     * @param regEx      正则表达式
     * @param groupIndex 匹配组位置
     * @return
     */
    public static List<String> getMatchers(String content, String regEx, int groupIndex) {
        Matcher matcher = compile(regEx).matcher(content);
        List<String> results = new ArrayList<>();
        while (matcher.find()) {
            results.add(matcher.group(groupIndex));
        }
        return results;
    }

    /**
     * 获取所有匹配项
     *
     * @param content 文本
     * @param regEx   正则表达式
     * @param groups  匹配组位置
     * @return
     */
    public static List<List<String>> getMatchers(String content, String regEx, List<Integer> groups) {
        Matcher matcher = compile(regEx).matcher(content);
        List<List<String>> results = new ArrayList<>();
        while (matcher.find()) {
            List<String> matches = new ArrayList<>();
            for (Integer groupIndex : groups) {
                matches.add(matcher.group(groupIndex));
            }
            results.add(matches);
        }
        return results;
    }

    /**
     * 获取第一个匹配项
     *
     * @param content 文本
     * @param regEx   正则表达式
     * @param isGroup 是否只匹配第一组
     * @return
     */
    public static String getFirstMatcher(String content, String regEx, boolean isGroup) {
        return getFirstMatcher(content, regEx, isGroup ? 1 : 0);
    }

    /**
     * 获取第一个匹配项
     *
     * @param content    文本
     * @param regEx      正则表达式
     * @param groupIndex 匹配组位置
     * @return
     */
    public static String getFirstMatcher(String content, String regEx, int groupIndex) {
        Matcher matcher = compile(regEx).matcher(content);
        if (matcher.find()) {
            return matcher.group(groupIndex);
        }
        return null;
    }

    /**
     * 获取第一个匹配项
     *
     * @param content 文本
     * @param regEx   正则表达式
     * @param groups  匹配组位置
     * @return
     */
    public static List<String> getFirstMatcher(String content, String regEx, List<Integer> groups) {
        Matcher matcher = compile(regEx).matcher(content);
        List<String> matches = new ArrayList<>();
        if (matcher.find()) {
            for (Integer groupIndex : groups) {
                matches.add(matcher.group(groupIndex));
            }
        }
        return matches;
    }

    /**
     * 获取主机地址
     */
    public static String getHostFromURL(String url){
        return getFirstMatcher(url, "(?<=//|)((\\w)+\\.)+\\w+", false);
    }

    /**
     * 获取域名地址
     */
    public static String getDomainFromURL(String url){
        return getFirstMatcher(url, "(http|https)://(www.)?(\\w+(\\.)?)+", false);
    }

    /**
     * 获取第一个html元素
     */
    public static Element getFirstElement(Element element, String selector) {
        return element.selectFirst(selector);
    }

    /**
     * 获取第一个html元素
     */
    public static String getFirstHTMLBySelector(Element element, String selector) {
        element = getFirstElement(element, selector);
        return null == element ? null : element.html();
    }

    /**
     * 获取第一个html元素的outerHtml
     */
    public static String getFirstOuterHTMLBySelector(Element element, String selector) {
        element = getFirstElement(element, selector);
        return null == element ? null : element.outerHtml();
    }

    /**
     * 获取第一个html元素的text
     */
    public static String getFirstTextBySelector(Element element, String selector) {
        element = getFirstElement(element, selector);
        return null == element ? null : element.text();
    }

    /**
     * 获取第一个html元素的attr值
     */
    public static String getFirstAttrBySelector(Element element, String selector, String attr) {
        element = getFirstElement(element, selector);
        return null == element ? null : element.attr(attr);
    }

    /**
     * 获取html元素集合
     */
    public static List<Element> getElements(Element element, String selector) {
        return element.select(selector);
    }

    /**
     * 获取html元素集合
     */
    public static List<String> getHTMLBySelector(Element element, String selector) {
        Elements elements = element.select(selector);
        List<String> result = new ArrayList<>();
        for (Element elem : elements) {
            result.add(elem.html());
        }
        return result;
    }

    /**
     * 获取html元素outerHtml集合
     */
    public static List<String> getOuterHTMLBySelector(Element element, String selector) {
        Elements elements = element.select(selector);
        List<String> result = new ArrayList<>();
        for (Element elem : elements) {
            result.add(elem.outerHtml());
        }
        return result;
    }

    /**
     * 获取html元素text集合
     */
    public static List<String> getTextBySelector(Element element, String selector) {
        Elements elements = element.select(selector);
        List<String> result = new ArrayList<>();
        for (Element elem : elements) {
            result.add(elem.text());
        }
        return result;
    }

    /**
     * 获取html元素attr值集合
     */
    public static List<String> getAttrBySelector(Element element, String selector, String attr) {
        Elements elements = element.select(selector);
        List<String> result = new ArrayList<>();
        for (Element elem : elements) {
            result.add(elem.attr(attr));
        }
        return result;
    }

    public static Object getValueByJsonPath(Object root, String jsonPath) {
        return JSONPath.eval(root, jsonPath);
    }

    public static List<String> getValuesByXPath(Element element, String xpath) {
        return Xsoup.select(element, xpath).list();
    }

    public static List<String> getValuesByXPath(Elements elements, String xpath) {
        return Xsoup.select(elements.html(), xpath).list();
    }

    public static String getValueByXPath(Element element, String xpath) {
        return Xsoup.select(element, xpath).get();
    }

    public static String getValueByXPath(Elements elements, String xpath) {
        return Xsoup.select(elements.html(), xpath).get();
    }

    public static String getElementByXPath(Element element, String xpath) {
        return Xsoup.select(element, xpath).get();
    }

    public static JXDocument createByUrl(String url) {
        return JXDocument.createByUrl(url);
    }

    public static JXDocument create(String html) {
        return JXDocument.create(html);
    }

    public static JXDocument create(Document document) {
        return JXDocument.create(document);
    }

    public static String getTextByNodeWithXpath(JXNode node, String xpath) {
        if (null == node || StringUtil.isEmpty(xpath)) {
            return "";
        }
        JXNode jxNode = node.selOne(xpath);
        return getText(jxNode);
    }

    public static String getHrefByNodeWithXpath(JXNode node, String xpath) {
        if (null == node || StringUtil.isEmpty(xpath)) {
            return "";
        }
        JXNode jxNode = node.selOne(xpath);
        return getText(jxNode);
    }

    public static List<String> getTextsByNodeWithXpath(JXNode node, String xpath) {
        if (null == node || StringUtil.isEmpty(xpath)) {
            return new ArrayList<>();
        }
        return node.sel(xpath).stream().map(jxNode -> {
            return getText(jxNode);
        }).collect(Collectors.toList());
    }

    private static String getText(JXNode jxNode) {
        String result = "";
        if (null != jxNode) {
            if (jxNode.isElement())
                result = jxNode.asElement().text();
            if (jxNode.isString())
                result = jxNode.toString();
            if (jxNode.isBoolean())
                result = jxNode.asBoolean().toString();
            if (jxNode.isNumber())
                result = jxNode.asDouble().toString();
            if (jxNode.isDate())
                result = DateUtil.formatDate(jxNode.asDate());
        }
        return result;
    }

    /**
     * html转成text文本
     *
     * @param inputString html文本
     */
    public static String html2Text(String inputString) {
        String htmlStr = inputString;
        String textStr = "";
        Pattern p_script;
        Matcher m_script;
        Pattern p_style;
        Matcher m_style;
        Pattern p_html;
        Matcher m_html;
        try {
            // 定义script的正则表达式{或<script>]*?>[\s\S]*?<\/script>}
            String regEx_script = "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>";
            // 定义style的正则表达式{或<style>]*?>[\s\S]*?<\/style>}
            String regEx_style = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>";
            // 定义HTML标签的正则表达式
            String regEx_html = "<[^>]+>";

            p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE);
            m_script = p_script.matcher(htmlStr);
            htmlStr = m_script.replaceAll(""); // 过滤script标签

            p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE);
            m_style = p_style.matcher(htmlStr);
            htmlStr = m_style.replaceAll(""); // 过滤style标签

            p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
            m_html = p_html.matcher(htmlStr);
            htmlStr = m_html.replaceAll(""); // 过滤html标签

            textStr = htmlStr;
        } catch (Exception e) {
            log.error("html转成text文本异常：{}", e.getMessage());
        }

        return textStr;
    }

    /**
     * 剔除图片元素
     */
    public static String wipeOutImageHtml(String inputString) {
        String htmlStr = inputString;
        String textStr = "";
        Pattern p_html;
        Matcher m_html;
        try {
            // 定义HTML 图片标签的正则表达式
            String regEx_html = "<img [^>]+>";
            p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
            m_html = p_html.matcher(htmlStr);
            htmlStr = m_html.replaceAll(""); // 过滤html标签
            textStr = htmlStr;
        } catch (Exception e) {
            log.error("剔除图片元素异常：{}", e.getMessage());
        }
        return textStr;
    }

    /**
     * 提取纯文本
     *
     * @param html   代码
     * @param length 提取文本长度
     */
    public static String previewText(String html, int length) {
        if (StringUtil.isEmpty(html)) {
            return html;
        }
        String text = Jsoup.clean(html, Whitelist.none()).trim();
        text = previewText(text);
        text = StringUtil.abbreviate(text, length);
        return text;
    }

    /**
     * 提取并替换图片视频src地址
     *
     * @param html    代码
     * @param replace 替换内容
     */
    public static List<String> generateSrc(String html, String[] replace) {
        if (StringUtil.isEmpty(html) || null == replace || replace.length == 0) {
            return Collections.singletonList(html);
        }
        List<String> result = new ArrayList<>();
        String define = "<img.*(src|SRC)=(\"|\')(.*?)(\"|\')[^>]*?>";
        List<String> source_list = grepTextByPattern(html, define, 3);
        for (String source : source_list) {
            result.add(replaceSrc(source, replace));
        }
        define = "<video.*(src|SRC)=(\"|\')(.*?)(\"|\')[^>]*?>";
        source_list = grepTextByPattern(html, define, 3);
        for (String source : source_list) {
            result.add(replaceSrc(source, replace));
        }
        define = "<source.*(src|SRC)=(\"|\')(.*?)(\"|\')[^>]*?>";
        source_list = grepTextByPattern(html, define, 3);
        for (String source : source_list) {
            result.add(replaceSrc(source, replace));
        }
        return result;
    }

    private static String replaceSrc(String source, String[] replace) {
        for (String rep : replace) {
            if (source.indexOf(rep) > -1) {
                source = source.substring(source.indexOf(rep));
            }
        }
        return source;
    }

    /**
     * 过滤HTML标记
     *
     * @param html 代码
     */
    public static String previewText(String html) {
        Pattern p_html;
        Matcher m_html;
        //删除脚本
        String regEx_html = "<script[^>]*?>.*?</script>";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("");
        //删除HTML
        regEx_html = "-->";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("");
        regEx_html = "<!--.*";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("");
        regEx_html = "&(quot|#34);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("\"");
        regEx_html = "&(amp|#38);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("&");
        regEx_html = "&(lt|#60);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("<");
        regEx_html = "&(gt|#62);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll(">");
        regEx_html = "&(nbsp|#160);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("   ");
        regEx_html = "&(iexcl|#161);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("\\xa1");
        regEx_html = "&(cent|#162);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("\\xa2");
        regEx_html = "&(pound|#163);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("\\xa3");
        regEx_html = "&(pound|#169);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("\\xa9");
        regEx_html = "&#(\\d+);";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("");
        regEx_html = "<(.[^>]*)>";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll("");
        regEx_html = "([\r|\n])[\\s]+";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll(" ");
        regEx_html = "([\r\n])[\\s]+";
        p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        m_html = p_html.matcher(html);
        html = m_html.replaceAll(" ");
        html.replaceAll("<", "");
        html.replaceAll(">", "");
        html.replaceAll("\r", " ");
        html.replaceAll("\n", " ");
        html.replaceAll("\r\n", " ");
        return html;
    }

    /**
     * 根据网页元素规则获取HTML中标签内容
     * <p>
     * 正则表达式格式：
     * "<h1.*?article-title\">(.*?)</h1>.*?<div.*?\"article-sub\">.*?<span>(.*?)</span>.*?<span>.*?</span>.*?</div>"
     * </p>
     *
     * @param html    Html源码
     * @param pattern 正则表达式
     */
    public static List<String> grepTextByPattern(String html, String pattern) {
        return grepTextByPattern(html, pattern, 0);
    }

    /**
     * 根据网页元素规则获取HTML中标签内容
     * <p>
     * 正则表达式格式：
     * "<h1.*?article-title\">(.*?)</h1>.*?<div.*?\"article-sub\">.*?<span>(.*?)</span>.*?<span>.*?</span>.*?</div>"
     * </p>
     *
     * @param html    Html源码
     * @param pattern 正则表达式
     * @param group   匹配位置,0表示全部匹配位置,1表示第一个()中匹配的元素...
     */
    public static List<String> grepTextByPattern(String html, String pattern, int group) {
        Matcher m = Pattern.compile(pattern).matcher(html);
        List<String> result = new ArrayList<>();
        while (m.find()) {
            //通过正则表达式占位符来获取需要的内容
            result.add(m.group(group));
        }
        return result;
    }

    /**
     * 根据指定规则获取HTML代码
     *
     * @param html Html源码
     * @param attr 元素属性: [html属性名,属性值]
     */
    public static List<String> grepHtmlByAttr(String html, String[] attr) {
        List<String> results = new ArrayList<>();
        try {
            Document doc = Jsoup.parse(html);
            List<Element> elements = new ArrayList<>();
            if ("id".equalsIgnoreCase(attr[0])) {
                elements.add(doc.getElementById(attr[1]));
            } else if ("class".equalsIgnoreCase(attr[0])) {
                elements.addAll(doc.getElementsByClass(attr[1]));
            } else {
                elements.addAll(doc.getElementsByAttributeValue(attr[0], attr[1]));
            }
            for (Element element : elements) {
                results.add(element.html());
            }
        } catch (Exception e) {
            log.error("抓取html代码异常：{}", e.getMessage());
        }
        return results;
    }

    /**
     * 将字符串渲染到客户端
     *
     * @param response 渲染对象
     * @param string   待渲染的字符串
     */
    public static String renderString(HttpServletResponse response, String string) {
        try {
            response.setContentType(ContentType.JSON.toString(StandardCharsets.UTF_8));
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write(string);
        } catch (IOException e) {
            log.error("客户端渲染异常：{}", e.getMessage());
        }
        return null;
    }

    /**
     * 获取request返回信息
     */
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
